// 套接字封装类
#pragma once
#include <unistd.h>
#include <cstring>
#include <arpa/inet.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include "log.hpp"

extern Log lg;

enum Tcp_Error
{
    Create_error = 1,
    Bind_error,
    Listen_error
};

const size_t ip_size = 30;
const int default_backlog = 10;

class tcp_socket
{
public:
    tcp_socket(int backlog = default_backlog)
        : _backlog(default_backlog), _sockfd(-1)
    {
        _sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_sockfd < 0)
        {
            perror("create socket");
            // lg(Fatal, "create sockfd error, errno:%d, strerr:%s", errno, strerror(errno));
            exit(Create_error);
        }
    }
    ~tcp_socket() { close(); }

    void set_no_time_wait()
    {
        int opt = 1;
        setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof opt);
    }

    int bind(uint16_t port)
    {
        struct sockaddr_in info;
        memset(&info, 0, sizeof info);
        info.sin_family = AF_INET;
        info.sin_addr.s_addr = INADDR_ANY;
        info.sin_port = htons(port);
        socklen_t len = sizeof info;
        return ::bind(_sockfd, (const sockaddr *)&info, len);

    }

    // 返回一个表示连接的套接字, 并且使用输出型参数返回请求方的ip和端口号
    int accept(std::string *src_ip, uint16_t *src_port)
    {
        struct sockaddr_in src;
        socklen_t len = sizeof src;
        int fd = ::accept(_sockfd, (sockaddr *)&src, &len);
        if (fd < 0)
           return fd;
        char ip[ip_size] = {0};
        *src_ip = inet_ntop(AF_INET, &(src.sin_addr), ip, sizeof ip);
        *src_port = ntohs(src.sin_port);
        return fd;
    }

    // 让当前套接字开启监听状态，等待连接
    int listen() { return ::listen(_sockfd, _backlog); }

    bool connect(const std::string &dst_ip, const uint16_t src_port)
    {
        struct sockaddr_in dst;
        memset(&dst, 0, sizeof dst);
        dst.sin_family = AF_INET;
        dst.sin_addr.s_addr = inet_addr(dst_ip.c_str());
        dst.sin_port = htons(src_port);
        socklen_t len = sizeof dst;
        int n = ::connect(_sockfd, (const sockaddr *)&dst, len);
        if (n < 0)
            return false;
        return true;
    }

    bool close()
    {
        if (_sockfd >= 0)
        {
            ::close(_sockfd);
            _sockfd = -1;
        }
        return true;
    }
    int fd() { return _sockfd; }
private:
    int _sockfd;
    int _backlog;
};
